use crate::{runtime::FuncBinds, Env, Value};

use super::Builtin;

pub fn bind(env: &mut FuncBinds) {
    env.bind_builtin("take", Take);
    env.bind_builtin("len", Len);
    env.bind_builtin("get", Get);
    env.bind_builtin("array", Array);
    env.bind_builtin("range", Range);
    env.bind_builtin("pushBack", PushBack);
}

struct PushBack;

impl Builtin for PushBack {
    fn call(&self, _: &Env, argv: &[Value]) -> Result<Value, ()> {
        let [Value::Array(arr), rest @ ..] = argv else {
            return Err(());
        };

        let mut arr = arr.clone();
        for each in rest {
            arr.push_back(each.clone());
        }

        Ok(Value::Array(arr))
    }
}

struct Range;

impl Builtin for Range {
    fn call(&self, _: &Env, argv: &[Value]) -> Result<Value, ()> {
        let n = match argv {
            [Value::Integer(n)] => *n,
            _ => {
                eprintln!("range: invalid arguments: {:?}", argv);
                return Err(());
            }
        };

        let mut range = Vec::with_capacity(n as usize);
        for i in 0..n {
            range.push(Value::Integer(i));
        }

        Ok(Value::Array(range.into()))
    }
}

struct Take;

impl Builtin for Take {
    fn call(&self, _: &Env, argv: &[Value]) -> Result<Value, ()> {
        let (n, vec) = match argv {
            [Value::Integer(n), Value::Array(arr)] => (*n as usize, arr),
            _ => {
                eprintln!("take: invalid arguments: {:?}", argv);
                return Err(());
            }
        };

        Ok(Value::Array(vec.take(n)))
    }
}

struct Len;

impl Builtin for Len {
    fn call(&self, _: &Env, argv: &[Value]) -> Result<Value, ()> {
        let n = match argv {
            [Value::Array(arr)] => arr.len(),
            [Value::Hash(map)] => map.len(),
            _ => {
                eprintln!("array_len: invalid arguments: {:?}", argv);
                return Err(());
            }
        };

        Ok(Value::Integer(n as i64))
    }
}

struct Get;

impl Builtin for Get {
    fn call(&self, _: &Env, argv: &[Value]) -> Result<Value, ()> {
        match argv {
            [Value::Array(arr), Value::Integer(n)] => {
                let Some(v) = arr.get(*n as usize) else {
                    eprintln!("get: out of range");
                    return Err(());
                };

                Ok(v.clone())
            }
            [Value::Array(arr), Value::Integer(n), or] => {
                let Some(v) = arr.get(*n as usize) else {
                    return Ok(or.clone());
                };

                Ok(v.clone())
            }
            _ => {
                eprintln!("get: invalid arguments: {:?}", argv);
                return Err(());
            }
        }
    }
}

struct Array;

impl Builtin for Array {
    fn call(&self, _: &Env, argv: &[Value]) -> Result<Value, ()> {
        let mut f = Vec::new();

        for each in argv {
            f.push(each.clone());
        }

        Ok(Value::Array(f.into()))
    }
}
